// arbitrary_positional_facade.hpp: a base class of repositional stream facade

// Copyright Takeshi Mouri 2006, 2007.
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

// See http://hamigaki.sourceforge.jp/libs/iostreams for library home page.

#ifndef HAMIGAKI_IOSTREAMS_ARBITRARY_POSITIONAL_FACADE_HPP
#define HAMIGAKI_IOSTREAMS_ARBITRARY_POSITIONAL_FACADE_HPP

#include <boost/config.hpp>
#include <boost/iostreams/detail/ios.hpp>
#include <boost/iostreams/positioning.hpp>
#include <boost/iostreams/traits.hpp>
#include <boost/assert.hpp>

namespace hamigaki { namespace iostreams {

	template<class Derived, class CharT, std::streamsize MaxBlockSize>
	class arbitrary_positional_facade;

	class core_access
	{
#if defined(BOOST_NO_MEMBER_TEMPLATE_FRIENDS)
	public:
#else
		template<class Derived, class CharT, std::streamsize MaxBlockSize>
		friend class arbitrary_positional_facade;

		friend struct device_operations;

		template<class Device>
		friend struct filter_operations;
#endif

		template<class RepositionalSource, class CharT>
		static std::streamsize read_blocks(
			RepositionalSource& src, CharT* s, std::streamsize n)
		{
			return src.read_blocks(s, n);
		}

		template<class RepositionalInputFilter, class Source>
		static std::streamsize read_blocks(
			RepositionalInputFilter& filter, Source& src,
			typename boost::iostreams::char_type_of<Source>::type* s,
			std::streamsize n)
		{
			return filter.read_blocks(src, s, n);
		}

		template<class RepositionalSink, class CharT>
		static std::streamsize write_blocks(
			RepositionalSink& sink, const CharT* s, std::streamsize n)
		{
			return sink.write_blocks(s, n);
		}

		template<class RepositionalOutputFilter, class Sink>
		static std::streamsize write_blocks(
			RepositionalOutputFilter& filter, Sink& sink,
			const typename boost::iostreams::char_type_of<Sink>::type* s,
			std::streamsize n)
		{
			return filter.write_blocks(sink, s, n);
		}

		template<class RepositionalSink, class CharT>
		static void close_with_flush(
			RepositionalSink& sink, const CharT* s, std::streamsize n)
		{
			return sink.close_with_flush(s, n);
		}

		template<class RepositionalOutputFilter, class Sink>
		static void close_with_flush(
			RepositionalOutputFilter& filter, Sink& sink,
			const typename boost::iostreams::char_type_of<Sink>::type* s,
			std::streamsize n)
		{
			return filter.close_with_flush(sink, s, n);
		}

		template<class RepositionalDevice>
		static std::streampos seek_blocks(
			RepositionalDevice& dev,
			boost::iostreams::stream_offset off, BOOST_IOS::seekdir way)
		{
			return dev.seek_blocks(off, way);
		}

		struct device_operations
		{
			template<class RepositionalDevice, class CharT>
			std::streamsize read_blocks(
				RepositionalDevice& t, CharT* s, std::streamsize n) const
			{
				return core_access::read_blocks(t, s, n);
			}

			template<class RepositionalDevice, class CharT>
			std::streamsize write_blocks(
				RepositionalDevice& t, const CharT* s, std::streamsize n) const
			{
				return core_access::write_blocks(t, s, n);
			}
		};

		template<class Device>
		struct filter_operations
		{
			typedef typename boost::iostreams::
				char_type_of<Device>::type char_type;

			Device* dev_ptr_;

			explicit filter_operations(Device& dev) : dev_ptr_(&dev) {}

			template<class RepositionalInputFilter>
			std::streamsize read_blocks(
				RepositionalInputFilter& t, char_type* s, std::streamsize n) const
			{
				return core_access::read_blocks(t, *dev_ptr_, s, n);
			}

			template<class RepositionalOutputFilter>
			std::streamsize write_blocks(
				RepositionalOutputFilter& t,
				const char_type* s, std::streamsize n) const
			{
				return core_access::write_blocks(t, *dev_ptr_, s, n);
			}
		};
	};

	template<class Derived, class CharT, std::streamsize MaxBlockSize>
	class arbitrary_positional_facade
	{
	private:
		typedef CharT char_type;

		Derived& derived()
		{
			return *static_cast<Derived*>(this);
		}

	protected:
		typedef arbitrary_positional_facade<
			Derived,CharT,MaxBlockSize> arbitrary_positional_facade_;

		void block_size(std::streamsize n)
		{
			block_size_ = n;
		}

	public:
		arbitrary_positional_facade() : block_size_(MaxBlockSize), count_(0)
		{
		}

		explicit arbitrary_positional_facade(std::streamsize block_size)
			: block_size_(block_size), count_(0)
		{
			BOOST_ASSERT(block_size_ <= MaxBlockSize);
		}

		std::streamsize read(char_type* s, std::streamsize n)
		{
			return read_impl(core_access::device_operations(), s, n);
		}

		template<class Source>
		std::streamsize read(Source& src, char_type* s, std::streamsize n)
		{
			return read_impl(core_access::filter_operations<Source>(src), s, n);
		}

		std::streamsize write(const char_type* s, std::streamsize n)
		{
			return write_impl(core_access::device_operations(), s, n);
		}

		template<class Sink>
		std::streamsize write(Sink& sink, const char_type* s, std::streamsize n)
		{
			return write_impl(core_access::filter_operations<Sink>(sink), s, n);
		}

		void close()
		{
			BOOST_ASSERT(count_ < block_size_);
			core_access::close_with_flush(derived(), buffer_, count_);
		}

		template<class Sink>
		void close(Sink& sink)
		{
			BOOST_ASSERT(count_ < block_size_);
			core_access::close_with_flush(derived(), sink, buffer_, count_);
		}

		std::streampos seek(
			boost::iostreams::stream_offset off, BOOST_IOS::seekdir way)
		{
			if (way == BOOST_IOS::beg)
			{
				core_access::seek_blocks(derived(), off/block_size_, way);

				std::streamsize skip =
					static_cast<std::streamsize>(off%block_size_);
				if (skip == 0)
					count_ = 0;
				else
				{
					std::streamsize res =
						core_access::read_blocks(derived(), buffer_, 1);
					if (res != 1)
						throw BOOST_IOSTREAMS_FAILURE("bad seek");
					count_ = block_size_ - skip;
				}
				return boost::iostreams::offset_to_position(off);
			}
			else if (way == BOOST_IOS::cur)
			{
				std::streampos pos =
					core_access::seek_blocks(
					derived(), (off-count_)/block_size_, way);

				std::streamsize skip =
					static_cast<std::streamsize>((off-count_)%block_size_);
				if (skip == 0)
				{
					count_ = 0;

					return boost::iostreams::offset_to_position(
						boost::iostreams::position_to_offset(pos) * block_size_);
				}
				else
				{
					std::streamsize res =
						core_access::read_blocks(derived(), buffer_, 1);
					if (res != 1)
						throw BOOST_IOSTREAMS_FAILURE("bad seek");
					count_ = block_size_ - skip;

					return boost::iostreams::offset_to_position(
						boost::iostreams::position_to_offset(pos) * block_size_
						+ block_size_-count_);
				}
			}
			else
			{
				std::streampos pos =
					core_access::seek_blocks(
					derived(), (off-block_size_+1)/block_size_, way);

				count_ =
					static_cast<std::streamsize>((-off)%block_size_);
				if (count_ == 0)
				{
					return boost::iostreams::offset_to_position(
						boost::iostreams::position_to_offset(pos) * block_size_);
				}
				else
				{
					std::streamsize res =
						core_access::read_blocks(derived(), buffer_, 1);
					if (res != 1)
						throw BOOST_IOSTREAMS_FAILURE("bad seek");

					return boost::iostreams::offset_to_position(
						boost::iostreams::position_to_offset(pos) * block_size_
						+ block_size_-count_);
				}
			}
		}

	private:
		char_type buffer_[MaxBlockSize];
		std::streamsize block_size_;
		std::streamsize count_;

		template<class Op>
		std::streamsize read_impl(const Op& op, char_type* s, std::streamsize n)
		{
			std::streamsize total = 0;

			if (count_ != 0)
			{
				std::streamsize amt = (std::min)(n, count_);
				char_type* start = buffer_ + (block_size_ - count_);
				s = std::copy(start, start+amt, s);
				n -= amt;
				count_ -= amt;
				total += amt;
			}

			if (n >= block_size_)
			{
				BOOST_ASSERT(count_ == 0);

				std::streamsize request = n/block_size_;
				std::streamsize res =
					op.read_blocks(derived(), s, request);

				if (res != -1)
				{
					s += res;
					n -= res;
					total += res;
				}

				if (res < request*block_size_)
					return total != 0 ? total : -1;
			}

			if (n != 0)
			{
				BOOST_ASSERT(n < block_size_);
				BOOST_ASSERT(count_ == 0);

				std::streamsize res =
					op.read_blocks(derived(), buffer_, 1);

				if (res > 0)
				{
					s = std::copy(buffer_, buffer_+n, s);
					count_ = block_size_ - n;
					total += n;
				}
			}

			return total != 0 ? total : -1;
		}

		template<class Op>
		std::streamsize write_impl(
			const Op& op, const char_type* s, std::streamsize n)
		{
			std::streamsize total = 0;

			if (count_ != 0)
			{
				std::streamsize amt = (std::min)(n, block_size_-count_);
				std::copy(s, s+amt, buffer_+count_);
				s += amt;
				n -= amt;
				count_ += amt;
				total += amt;

				if (count_ == block_size_)
				{
					op.write_blocks(derived(), buffer_, 1);
					count_ = 0;
				}
			}

			if (n >= block_size_)
			{
				BOOST_ASSERT(count_ == 0);

				std::streamsize request = n/block_size_;
				op.write_blocks(derived(), s, request);

				std::streamsize amt = request*block_size_;
				s += amt;
				n -= amt;
				total += amt;
			}

			if (n != 0)
			{
				BOOST_ASSERT(n < block_size_);
				BOOST_ASSERT(count_ == 0);

				std::copy(s, s+n, buffer_);
				count_ = n;
				total += n;
			}

			return total != 0 ? total : -1;
		}
	};


} } // End namespaces iostreams, hamigaki.

#endif // HAMIGAKI_IOSTREAMS_ARBITRARY_POSITIONAL_FACADE_HPP
